/*================================================== Copyright 20013-2014 司徒正美 and other contributors //www.cnblogs.com/rubylouvre/ //github.com/RubyLouvre //weibo.com/jslouvre/ Released under the MIT license avalon 1.3 2014.5.29 ==================================================*/ (function(DOC) { var Registry = {} //将函数曝光到此对象上,方便访问器收集依赖 var expose = new Date - 0 var subscribers = "$" + expose var window = this || (0, eval)("this") ////addyosmani.com/blog/understanding-mvvm-a-guide-for-javascript-developers/ var otherRequire = window.require var otherDefine = window.define var stopRepeatAssign = false var rword = /[^, ]+/g //切割字符串为一个个小块,以空格或豆号分开它们,结合replace实现字符串的forEach var class2type = {} var oproto = Object.prototype var ohasOwn = oproto.hasOwnProperty var prefix = "ms-" var W3C = window.dispatchEvent var root = DOC.documentElement var serialize = oproto.toString var ap = Array.prototype var aslice = ap.slice var head = DOC.head || DOC.getElementsByTagName("head")[0] //HEAD元素 var documentFragment = DOC.createDocumentFragment() var DONT_ENUM = "propertyIsEnumerable,isPrototypeOf,hasOwnProperty,toLocaleString,toString,valueOf,constructor".split(",") "Boolean Number String Function Array Date RegExp Object Error".replace(rword, function(name) { class2type["[object " + name + "]"] = name.toLowerCase() }) var rnative = /\[native code\]/ var rchecktype = /^(?:object|array)$/ var rwindow = /^\[object (Window|DOMWindow|global)\]$/ function noop() { } function log(a) { window.console && console.log(W3C ? a : a + "") } /********************************************************************* * 命名空间与工具函? * **********************************************************************/ avalon = function(el) { //创建jQuery式的无new 实例化结? return new avalon.init(el) } avalon.init = function(el) { this[0] = this.element = el } avalon.fn = avalon.prototype = avalon.init.prototype //率先添加三个判定类型的方? function getType(obj) { //取得类型 if (obj == null) { return String(obj) } // 早期的webkit内核浏览器实现了已废弃的ecma262v4标准,可以将正则字面量当作函数使用,因此typeof在判定正则时会返回function return typeof obj === "object" || typeof obj === "function" ? class2type[serialize.call(obj)] || "object" : typeof obj } avalon.type = getType avalon.isWindow = function(obj) { if (!obj) return false // 利用IE678 window == document为true,document == window竟然为false的神奇特? // 标准浏览器及IE9,IE10等使?正则检? return obj == obj.document && obj.document != obj } function isWindow(obj) { return rwindow.test(serialize.call(obj)) } if (isWindow(window)) { avalon.isWindow = isWindow } //判定是否是一个朴素的javascript对象(Object),不是DOM对象,不是BOM对象,不是自定义类的实例? avalon.isPlainObject = function(obj) { if (getType(obj) !== "object" || obj.nodeType || this.isWindow(obj)) { return false } try { if (obj.constructor && !ohasOwn.call(obj.constructor.prototype, "isPrototypeOf")) { return false } } catch (e) { return false } return true } if (rnative.test(Object.getPrototypeOf)) { avalon.isPlainObject = function(obj) { return !!obj && typeof obj === "object" && Object.getPrototypeOf(obj) === oproto } } avalon.mix = avalon.fn.mix = function() { var options, name, src, copy, copyIsArray, clone, target = arguments[0] || {}, i = 1, length = arguments.length, deep = false // 如果第一个参数为布尔,判定是否深拷? if (typeof target === "boolean") { deep = target target = arguments[1] || {} i++ } //确保接受方为一个复杂的数据类型 if (typeof target !== "object" && getType(target) !== "function") { target = {} } //如果只有一个参数,那么新成员添加于mix所在的对象? if (i === length) { target = this i-- } for (; i < length; i++) { //只处理非空参? if ((options = arguments[i]) != null) { for (name in options) { src = target[name] copy = options[name] // 防止环引? if (target === copy) { continue } if (deep && copy && (avalon.isPlainObject(copy) || (copyIsArray = Array.isArray(copy)))) { if (copyIsArray) { copyIsArray = false clone = src && Array.isArray(src) ? src : [] } else { clone = src && avalon.isPlainObject(src) ? src : {} } target[name] = avalon.mix(deep, clone, copy) } else if (copy !== void 0) { target[name] = copy } } } } return target } var eventMap = avalon.eventMap = {} function resetNumber(a, n, end) { //用于模拟slice, splice的效? if ((a === +a) && !(a % 1)) { //如果是整? if (a < 0) { a = a * -1 >= n ? 0 : a + n } else { a = a > n ? n : a } } else { a = end ? n : 0 } return a } function oneObject(array, val) { if (typeof array === "string") { array = array.match(rword) || [] } var result = {}, value = val !== void 0 ? val : 1 for (var i = 0, n = array.length; i < n; i++) { result[array[i]] = value } return result } avalon.mix({ rword: rword, subscribers: subscribers, version: 1.3, ui: {}, log: log, slice: W3C ? function(nodes, start, end) { return aslice.call(nodes, start, end) } : function(nodes, start, end) { var ret = [], n = nodes.length start = resetNumber(start, n) end = resetNumber(end, n, 1) for (var i = start; i < end; ++i) { ret[i - start] = nodes[i] } return ret }, noop: noop, error: function(str, e) { //如果不用Error对象封装一下,str在控制台下可能会乱码 throw new (e || Error)(str) }, oneObject: oneObject, /* avalon.range(10) => [0, 1, 2, 3, 4, 5, 6, 7, 8, 9] avalon.range(1, 11) => [1, 2, 3, 4, 5, 6, 7, 8, 9, 10] avalon.range(0, 30, 5) => [0, 5, 10, 15, 20, 25] avalon.range(0, -10, -1) => [0, -1, -2, -3, -4, -5, -6, -7, -8, -9] avalon.range(0) => []*/ range: function(start, end, step) { // 用于生成整数数组 step || (step = 1) if (end == null) { end = start || 0 start = 0 } var index = -1, length = Math.max(0, Math.ceil((end - start) / step)), result = Array(length) while (++index < length) { result[index] = start start += step } return result }, bind: function(el, type, fn, phase) { // 绑定事件 var callback = W3C ? fn : function(e) { return fn.call(el, fixEvent(e)) } if (W3C) { el.addEventListener(eventMap[type] || type, callback, !!phase) } else { el.attachEvent("on" + type, callback) } return callback }, unbind: W3C ? function(el, type, fn, phase) { //卸载事件 el.removeEventListener(eventMap[type] || type, fn || noop, !!phase) } : function(el, type, fn) { el.detachEvent("on" + type, fn || noop) }, css: function(node, name, value) { if (node instanceof avalon) { node = node[0] } var prop = /[_-]/.test(name) ? camelize(name) : name name = avalon.cssName(prop) || prop if (value === void 0 || typeof value === "boolean") { //获取样式 var fn = cssHooks[prop + ":get"] || cssHooks["@:get"] var val = fn(node, name) return value === true ? parseFloat(val) || 0 : val } else if (value === "") { //请除样式 node.style[name] = "" } else { //设置样式 if (value == null || value !== value) { return } if (isFinite(value) && !avalon.cssNumber[prop]) { value += "px" } fn = cssHooks[prop + ":set"] || cssHooks["@:set"] fn(node, name, value) } }, each: function(obj, fn) { if (obj) { //排除null, undefined var i = 0 if (isArrayLike(obj)) { for (var n = obj.length; i < n; i++) { fn(i, obj[i]) } } else { for (i in obj) { if (obj.hasOwnProperty(i)) { fn(i, obj[i]) } } } } }, getWidgetData: function(elem, prefix) { var raw = avalon(elem).data() var result = {} for (var i in raw) { if (i.indexOf(prefix) === 0) { result[i.replace(prefix, "").replace(/\w/, function(a) { return a.toLowerCase() })] = raw[i] } } return result }, Array: { ensure: function(target, item) { //只有当前数组不存在此元素时只添加? if (target.indexOf(item) === -1) { target.push(item) } return target }, removeAt: function(target, index) { //移除数组中指定位置的元素,返回布尔表示成功与否? return !!target.splice(index, 1).length }, remove: function(target, item) { //移除数组中第一个匹配传参的那个元素,返回布尔表示成功与否? var index = target.indexOf(item) if (~index) return avalon.Array.removeAt(target, index) return false } } }) function generateID() { //生成UUID //stackoverflow.com/questions/105034/how-to-create-a-guid-uuid-in-javascript return "avalon" + Math.random().toString(36).substring(2, 15) + Math.random().toString(36).substring(2, 15) } //只让节点集合,纯数组,arguments与拥有非负整数的length属性的纯JS对象通过 function isArrayLike(obj) { if (obj && typeof obj === "object" && !avalon.isWindow(obj)) { var n = obj.length if (+n === n && !(n % 1) && n >= 0) { //检测length属性是否为非负整数 try { if ({}.propertyIsEnumerable.call(obj, "length") === false) { //如果是原生对? return Array.isArray(obj) || /^\s?function/.test(obj.item || obj.callee) } return true } catch (e) { //IE的NodeList直接抛错 return true } } } return false } //视浏览器情况采用最快的异步回调(在avalon.ready里,还有一个分支,用于处理IE6-9) avalon.nextTick = window.setImmediate ? setImmediate.bind(window) : function(callback) { setTimeout(callback, 0) //IE10-11 or W3C } /********************************************************************* * modelFactory * **********************************************************************/ var VMODELS = avalon.vmodels = {} avalon.define = function(id, factory) { if (VMODELS[id]) { log("warning: " + id + " 已经存在于avalon.vmodels?) } var scope = { $watch: noop } factory(scope) //得到所有定? var model = modelFactory(scope) //偷天换日,将scope换为model stopRepeatAssign = true factory(model) stopRepeatAssign = false model.$id = id return VMODELS[id] = model } function modelFactory(scope, model) { if (Array.isArray(scope)) { var arr = scope.concat() //原数组的作为新生成的监控数组?model而存? scope.length = 0 var collection = Collection(scope) collection.push.apply(collection, arr) return collection } if (typeof scope.nodeType === "number") { return scope } var vmodel = {} //要返回的对象 model = model || {} //放置$model上的属? var accessingProperties = {} //监控属? var normalProperties = {} //普通属? var computedProperties = [] //计算属? var watchProperties = arguments[2] || {} //强制要监听的属? var skipArray = scope.$skipArray //要忽略监控的属? for (var i = 0, name; name = skipProperties[i++]; ) { if (typeof name !== "string") { log("warning:$skipArray[" + name + "] must be a string") } delete scope[name] normalProperties[name] = true } if (Array.isArray(skipArray)) { for (var i = 0, name; name = skipArray[i++]; ) { normalProperties[name] = true } } for (var i in scope) { loopModel(i, scope[i], model, normalProperties, accessingProperties, computedProperties, watchProperties) } vmodel = defineProperties(vmodel, descriptorFactory(accessingProperties), normalProperties) //生成一个空的ViewModel for (var name in normalProperties) { vmodel[name] = normalProperties[name] } watchProperties.vmodel = vmodel vmodel.$model = model vmodel.$events = {} vmodel.$id = generateID() vmodel.$accessors = accessingProperties vmodel[subscribers] = [] for (var i in Observable) { var fn = Observable[i] if (!W3C) { //在IE6-8下,VB对象的方法里的this并不指向自身,需要用bind处理一? fn = fn.bind(vmodel) } vmodel[i] = fn } vmodel.hasOwnProperty = function(name) { return name in vmodel.$model } for (var i = 0, fn; fn = computedProperties[i++]; ) { //最后强逼计算属?计算自己的? Registry[expose] = fn fn() collectSubscribers(fn) delete Registry[expose] } return vmodel } var skipProperties = String("$id,$watch,$unwatch,$fire,$events,$model,$skipArray,$accessors," + subscribers).match(rword) var isEqual = Object.is || function(v1, v2) { if (v1 === 0 && v2 === 0) { return 1 / v1 === 1 / v2 } else if (v1 !== v1) { return v2 !== v2 } else { return v1 === v2 } } function safeFire(a, b, c, d) { if (a.$events) { Observable.$fire.call(a, b, c, d) } } var descriptorFactory = W3C ? function(obj) { var descriptors = {} for (var i in obj) { descriptors[i] = { get: obj[i], set: obj[i], enumerable: true, configurable: true } } return descriptors } : function(a) { return a } function loopModel(name, val, model, normalProperties, accessingProperties, computedProperties, watchProperties) { model[name] = val if (normalProperties[name] || (val && val.nodeType)) { //如果是元素节点或在全局的skipProperties里或在当前的$skipArray? return normalProperties[name] = val } if (name.charAt(0) === "$" && !watchProperties[name]) { //如果?开头,并且不在watchProperties? return normalProperties[name] = val } var valueType = getType(val) if (valueType === "function") { //如果是函数,也不用监? return normalProperties[name] = val } var accessor, oldArgs if (valueType === "object" && typeof val.get === "function" && Object.keys(val).length <= 2) { var setter = val.set, getter = val.get accessor = function(newValue) { //创建计算属性,因变量,基本上由其他监控属性触发其改变 var vmodel = watchProperties.vmodel var value = model[name], preValue = value if (arguments.length) { if (stopRepeatAssign) { return } if (typeof setter === "function") { var backup = vmodel.$events[name] vmodel.$events[name] = [] //清空回调,防止内部冒泡而触发多?fire setter.call(vmodel, newValue) vmodel.$events[name] = backup } if (!isEqual(oldArgs, newValue)) { oldArgs = newValue newValue = model[name] = getter.call(vmodel) //同步$model withProxyCount && updateWithProxy(vmodel.$id, name, newValue) //同步循环绑定中的代理VM notifySubscribers(accessor) //通知顶层改变 safeFire(vmodel, name, newValue, preValue) //触发$watch回调 } } else { if (avalon.openComputedCollect) { // 收集视图刷新函数 collectSubscribers(accessor) } newValue = model[name] = getter.call(vmodel) if (!isEqual(value, newValue)) { oldArgs = void 0 safeFire(vmodel, name, newValue, preValue) } return newValue } } computedProperties.push(accessor) } else if (rchecktype.test(valueType)) { accessor = function(newValue) { //子ViewModel或监控数? var realAccessor = accessor.$vmodel, preValue = realAccessor.$model if (arguments.length) { if (stopRepeatAssign) { return } if (!isEqual(preValue, newValue)) { newValue = accessor.$vmodel = updateVModel(realAccessor, newValue, valueType) var fn = rebindings[newValue.$id] fn && fn() //更新视图 var parent = watchProperties.vmodel model[name] = newValue.$model //同步$model notifySubscribers(realAccessor) //通知顶层改变 safeFire(parent, name, model[name], preValue) //触发$watch回调 } } else { collectSubscribers(realAccessor) //收集视图函数 return realAccessor } } accessor.$vmodel = val.$model ? val : modelFactory(val, val) model[name] = accessor.$vmodel.$model } else { accessor = function(newValue) { //简单的数据类型 var preValue = model[name] if (arguments.length) { if (!isEqual(preValue, newValue)) { model[name] = newValue //同步$model var vmodel = watchProperties.vmodel withProxyCount && updateWithProxy(vmodel.$id, name, newValue) //同步循环绑定中的代理VM notifySubscribers(accessor) //通知顶层改变 safeFire(vmodel, name, newValue, preValue) //触发$watch回调 } } else { collectSubscribers(accessor) //收集视图函数 return preValue } } model[name] = val } accessor[subscribers] = [] //订阅者数? accessingProperties[name] = accessor } //with绑定生成的代理对象储存池 var withProxyPool = {} var withProxyCount = 0 var rebindings = {} function updateWithProxy($id, name, val) { var pool = withProxyPool[$id] if (pool && pool[name]) { pool[name].$val = val } } function updateVModel(a, b, valueType) { //a为原来的VM?b为新数组或新对象 if (valueType === "array") { if (!Array.isArray(b)) { return a //fix //github.com/RubyLouvre/avalon/issues/261 } var bb = b.concat() a.clear() a.push.apply(a, bb) return a } else { var iterators = a[subscribers] || [] if (withProxyPool[a.$id]) { withProxyCount-- delete withProxyPool[a.$id] } var ret = modelFactory(b) rebindings[ret.$id] = function(data) { while (data = iterators.shift()) { (function(el) { if (el.type) { //重新绑定 avalon.nextTick(function() { el.rollback && el.rollback() //还原 ms-with ms-on bindingHandlers[el.type](el, el.vmodels) }) } })(data) } delete rebindings[ret.$id] } return ret } } //===================修复浏览器对Object.defineProperties的支?================ var defineProperty = Object.defineProperty //如果浏览器不支持ecma262v5的Object.defineProperties或者存在BUG,比如IE8 //标准浏览器使用__defineGetter__, __defineSetter__实现 try { defineProperty({}, "_", { value: "x" }) var defineProperties = Object.defineProperties } catch (e) { if ("__defineGetter__" in avalon) { defineProperty = function(obj, prop, desc) { if ('value' in desc) { obj[prop] = desc.value } if ("get" in desc) { obj.__defineGetter__(prop, desc.get) } if ('set' in desc) { obj.__defineSetter__(prop, desc.set) } return obj } defineProperties = function(obj, descs) { for (var prop in descs) { if (descs.hasOwnProperty(prop)) { defineProperty(obj, prop, descs[prop]) } } return obj } } } //IE6-8使用VBScript类的set get语句实现 if (!defineProperties && window.VBArray) { window.execScript([ "Function parseVB(code)", "\tExecuteGlobal(code)", "End Function", "Dim VBClassBodies", "Set VBClassBodies=CreateObject(\"Scripting.Dictionary\")", "Function findOrDefineVBClass(name,body)", "\tDim found", "\tfound=\"\"", "\tFor Each key in VBClassBodies", "\t\tIf body=VBClassBodies.Item(key) Then", "\t\t\tfound=key", "\t\t\tExit For", "\t\tEnd If", "\tnext", "\tIf found=\"\" Then", "\t\tparseVB(\"Class \" + name + body)", "\t\tVBClassBodies.Add name, body", "\t\tfound=name", "\tEnd If", "\tfindOrDefineVBClass=found", "End Function" ].join("\n"), "VBScript") function VBMediator(accessingProperties, name, value) { var accessor = accessingProperties[name] if (typeof accessor == "function") { if (arguments.length === 3) { accessor(value) } else { return accessor() } } } defineProperties = function(name, accessingProperties, normalProperties) { var className = "VBClass" + setTimeout("1"), buffer = [] buffer.push( "\r\n\tPrivate [__data__], [__proxy__]", "\tPublic Default Function [__const__](d, p)", "\t\tSet [__data__] = d: set [__proxy__] = p", "\t\tSet [__const__] = Me", //链式调用 "\tEnd Function") //添加普通属?因为VBScript对象不能像JS那样随意增删属性,必须在这里预先定义好 for (name in normalProperties) { buffer.push("\tPublic [" + name + "]") } buffer.push("\tPublic [" + 'hasOwnProperty' + "]") //添加访问器属? for (name in accessingProperties) { if (!(name in normalProperties)) { //防止重复定义 buffer.push( //由于不知对方会传入什?因此set, let都用? "\tPublic Property Let [" + name + "](val" + expose + ")", //setter "\t\tCall [__proxy__]([__data__], \"" + name + "\", val" + expose + ")", "\tEnd Property", "\tPublic Property Set [" + name + "](val" + expose + ")", //setter "\t\tCall [__proxy__]([__data__], \"" + name + "\", val" + expose + ")", "\tEnd Property", "\tPublic Property Get [" + name + "]", //getter "\tOn Error Resume Next", //必须优先使用set语句,否则它会误将数组当字符串返回 "\t\tSet[" + name + "] = [__proxy__]([__data__],\"" + name + "\")", "\tIf Err.Number <> 0 Then", "\t\t[" + name + "] = [__proxy__]([__data__],\"" + name + "\")", "\tEnd If", "\tOn Error Goto 0", "\tEnd Property") } } buffer.push("End Class") var code = buffer.join("\r\n"), realClassName = window['findOrDefineVBClass'](className, code) //如果该VB类已定义,返回类名。否则用className创建一个新类? if (realClassName == className) { window.parseVB([ "Function " + className + "Factory(a, b)", //创建实例并传入两个关键的参数 "\tDim o", "\tSet o = (New " + className + ")(a, b)", "\tSet " + className + "Factory = o", "End Function" ].join("\r\n")) } var ret = window[realClassName + "Factory"](accessingProperties, VBMediator) //得到其产? return ret //得到其产? } } /********************************************************************* * ecma262 v5语法补丁 * **********************************************************************/ if (!"司徒正美".trim) { var rtrim = /^[\s\uFEFF\xA0]+|[\s\uFEFF\xA0]+$/g String.prototype.trim = function() { return this.replace(rtrim, "") } } for (var i in { toString: 1 }) { DONT_ENUM = false } if (!Object.keys) { Object.keys = function(obj) { //ecma262v5 15.2.3.14 var result = [] for (var key in obj) if (obj.hasOwnProperty(key)) { result.push(key) } if (DONT_ENUM && obj) { for (var i = 0; key = DONT_ENUM[i++]; ) { if (obj.hasOwnProperty(key)) { result.push(key) } } } return result } } if (!Array.isArray) { Array.isArray = function(a) { return a && getType(a) === "array" } } if (!noop.bind) { Function.prototype.bind = function(scope) { if (arguments.length < 2 && scope === void 0) return this var fn = this, argv = arguments return function() { var args = [], i for (i = 1; i < argv.length; i++) args.push(argv[i]) for (i = 0; i < arguments.length; i++) args.push(arguments[i]) return fn.apply(scope, args) } } } function iterator(vars, body, ret) { var fun = 'for(var ' + vars + 'i=0,n = this.length; i < n; i++){' + body.replace('_', '((i in this) && fn.call(scope,this[i],i,this))') + '}' + ret return Function("fn,scope", fun) } if (!rnative.test([].map)) { avalon.mix(ap, { //定位操作,返回数组中第一个等于给定参数的元素的索引值? indexOf: function(item, index) { var n = this.length, i = ~~index if (i < 0) i += n for (; i < n; i++) if (this[i] === item) return i return -1 }, //定位操作,同上,不过是从后遍历? lastIndexOf: function(item, index) { var n = this.length, i = index == null ? n - 1 : index if (i < 0) i = Math.max(0, n + i) for (; i >= 0; i--) if (this[i] === item) return i return -1 }, //迭代操作,将数组的元素挨个儿传入一个函数中执行。Prototype.js的对应名字为each? forEach: iterator("", '_', ""), //迭代?在数组中的每个项上运行一个函数,如果此函数的值为真,则此元素作为新数组的元素收集起来,并返回新数? filter: iterator('r=[],j=0,', 'if(_)r[j++]=this[i]', 'return r'), //收集操作,将数组的元素挨个儿传入一个函数中执行,然后把它们的返回值组成一个新数组返回。Prototype.js的对应名字为collect? map: iterator('r=[],', 'r[i]=_', 'return r'), //只要数组中有一个元素满足条件(放进给定函数返回true),那么它就返回true。Prototype.js的对应名字为any? some: iterator("", 'if(_)return true', 'return false'), //只有数组中的元素都满足条件(放进给定函数返回true),它才返回true。Prototype.js的对应名字为all? every: iterator("", 'if(!_)return false', 'return true') }) } function fixContains(a, b) { if (b) { while ((b = b.parentNode)) { if (b === a) { return true; } } } return false; } if (!root.contains) { //safari5+是把contains方法放在Element.prototype上而不是Node.prototype Node.prototype.contains = function(arg) { return !!(this.compareDocumentPosition(arg) & 16) } } if (!DOC.contains) { //IE6-11的文档对象没有contains DOC.contains = function(b) { return fixContains(this, b) } } if (!root.outerHTML && window.HTMLElement) { //firefox ?1时才有outerHTML HTMLElement.prototype.__defineGetter__("outerHTML", function() { cinerator.textContent = "" cinerator.appendChild(this) var str = this.innerHTML cinerator.textContent = "" return str }); } /********************************************************************* * 配置模块 * **********************************************************************/ function kernel(settings) { for (var p in settings) { if (!ohasOwn.call(settings, p)) continue var val = settings[p] if (typeof kernel.plugins[p] === "function") { kernel.plugins[p](val) } else if (typeof kernel[p] === "object") { avalon.mix(kernel[p], val) } else { kernel[p] = val } } return this } var openTag, closeTag, rexpr, rexprg, rbind, rregexp = /[-.*+?^${}()|[\]\/\\]/g function escapeRegExp(target) { ////stevenlevithan.com/regex/xregexp/ //将字符串安全格式化为正则表达式的源码 return (target + "").replace(rregexp, "\\$&") } var plugins = { debug: function(open) { if (window.console) { if (!console._log) { console._log = console.log } console.log = open ? console._log : noop } }, loader: function(builtin) { window.define = builtin ? innerRequire.define : otherDefine window.require = builtin ? innerRequire : otherRequire }, interpolate: function(array) { openTag = array[0] closeTag = array[1] if (openTag === closeTag) { throw new SyntaxError("openTag!==closeTag") } else if (array + "" === "") { kernel.commentInterpolate = true } else { var test = openTag + "test" + closeTag var cinerator = DOC.createElement("div") cinerator.innerHTML = test if (cinerator.innerHTML !== test && cinerator.innerHTML.indexOf("<") !== 0) { throw new SyntaxError("此定界符不合?) } cinerator.innerHTML = "" } var o = escapeRegExp(openTag), c = escapeRegExp(closeTag) rexpr = new RegExp(o + "(.*?)" + c) rexprg = new RegExp(o + "(.*?)" + c, "g") rbind = new RegExp(o + ".*?" + c + "|\\sms-") } } kernel.plugins = plugins kernel.plugins['interpolate'](["{{", "}}"]) kernel.paths = {} kernel.shim = {} kernel.maxRepeatSize = 100 avalon.config = kernel /********************************************************************* * DOM API的高级封? * **********************************************************************/ function hyphen(target) { //转换为连字符线风? return target.replace(/([a-z\d])([A-Z]+)/g, "$1-$2").toLowerCase() } function camelize(target) { //转换为驼峰风? if (target.indexOf("-") < 0 && target.indexOf("_") < 0) { return target //提前判断,提高getStyle等的效率 } return target.replace(/[-_][^-_]/g, function(match) { return match.charAt(1).toUpperCase() }) } var rnospaces = /\S+/g avalon.fn.mix({ hasClass: function(cls) { var node = this[0] || {} if (node.nodeType === 1 && node.className) { return (" " + node.className + " ").indexOf(" " + cls + " ") > -1 } return false }, addClass: function(cls) { var node = this[0] || {} if (cls && typeof cls === "string" && node.nodeType === 1) { if (!node.className) { node.className = cls } else { var arr = node.className.match(rnospaces) cls.replace(rnospaces, function(a) { if (arr.indexOf(a) === -1) { arr.push(a) } }) node.className = arr.join(" ") } } return this }, removeClass: function(cls) { var node = this[0] || {} if (cls && typeof cls > "o" && node.nodeType === 1 && node.className) { var classNames = (cls || "").match(rnospaces) || [] var cl = classNames.length var set = " " + node.className.match(rnospaces).join(" ") + " " for (var c = 0; c < cl; c++) { set = set.replace(" " + classNames[c] + " ", " ") } node.className = set.slice(1, set.length - 1) } return this }, toggleClass: function(value, stateVal) { var state = stateVal, className, i = 0 var classNames = value.match(rnospaces) || [] var isBool = typeof stateVal === "boolean" while ((className = classNames[i++])) { state = isBool ? state : !this.hasClass(className) this[state ? "addClass" : "removeClass"](className) } return this }, attr: function(name, value) { if (arguments.length === 2) { this[0].setAttribute(name, value) return this } else { return this[0].getAttribute(name) } }, data: function(name, value) { name = "data-" + hyphen(name || "") switch (arguments.length) { case 2: this.attr(name, value) return this case 1: var val = this.attr(name) return parseData(val) case 0: var ret = {} ap.forEach.call(this[0].attributes, function(attr) { if (attr) { name = attr.name if (!name.indexOf("data-")) { name = camelize(name.slice(5)) ret[name] = parseData(attr.value) } } }) return ret } }, removeData: function(name) { name = "data-" + hyphen(name) this[0].removeAttribute(name) return this }, css: function(name, value) { if (avalon.isPlainObject(name)) { for (var i in name) { avalon.css(this, i, name[i]) } } else { var ret = avalon.css(this, name, value) } return ret !== void 0 ? ret : this }, position: function() { var offsetParent, offset, elem = this[0], parentOffset = { top: 0, left: 0 } if (!elem) { return } if (this.css("position") === "fixed") { offset = elem.getBoundingClientRect() } else { offsetParent = this.offsetParent() //得到真正的offsetParent offset = this.offset() // 得到正确的offsetParent if (offsetParent[0].tagName !== "HTML") { parentOffset = offsetParent.offset() } parentOffset.top += avalon.css(offsetParent[0], "borderTopWidth", true) parentOffset.left += avalon.css(offsetParent[0], "borderLeftWidth", true) } return { top: offset.top - parentOffset.top - avalon.css(elem, "marginTop", true), left: offset.left - parentOffset.left - avalon.css(elem, "marginLeft", true) } }, offsetParent: function() { var offsetParent = this[0].offsetParent || root while (offsetParent && (offsetParent.tagName !== "HTML") && avalon.css(offsetParent, "position") === "static") { offsetParent = offsetParent.offsetParent } return avalon(offsetParent || root) }, bind: function(type, fn, phase) { if (this[0]) { //此方法不会链 return avalon.bind(this[0], type, fn, phase) } }, unbind: function(type, fn, phase) { if (this[0]) { avalon.unbind(this[0], type, fn, phase) } return this }, val: function(value) { var node = this[0] if (node && node.nodeType === 1) { var get = arguments.length === 0 var access = get ? ":get" : ":set" var fn = valHooks[getValType(node) + access] if (fn) { var val = fn(node, value) } else if (get) { return (node.value || "").replace(/\r/g, "") } else { node.value = value } } return get ? val : this } }) function parseData(data) { try { data = data === "true" ? true : data === "false" ? false : data === "null" ? null : +data + "" === data ? +data : rbrace.test(data) ? parseJSON(data) : data } catch (e) { } return data } var rbrace = /(?:\{[\s\S]*\}|\[[\s\S]*\])$/, rvalidchars = /^[\],:{}\s]*$/, rvalidbraces = /(?:^|:|,)(?:\s*\[)+/g, rvalidescape = /\\(?:["\\\/bfnrt]|u[\da-fA-F]{4})/g, rvalidtokens = /"[^"\\\r\n]*"|true|false|null|-?(?:\d+\.|)\d+(?:[eE][+-]?\d+|)/g var parseJSON = window.JSON ? JSON.parse : function(data) { if (typeof data === "string") { data = data.trim(); if (data) { if (rvalidchars.test(data.replace(rvalidescape, "@") .replace(rvalidtokens, "]") .replace(rvalidbraces, ""))) { return (new Function("return " + data))(); } } avalon.error("Invalid JSON: " + data); } } //生成avalon.fn.scrollLeft, avalon.fn.scrollTop方法 avalon.each({ scrollLeft: "pageXOffset", scrollTop: "pageYOffset" }, function(method, prop) { avalon.fn[method] = function(val) { var node = this[0] || {}, win = getWindow(node), top = method === "scrollTop" if (!arguments.length) { return win ? (prop in win) ? win[prop] : root[method] : node[method] } else { if (win) { win.scrollTo(!top ? val : avalon(win).scrollLeft(), top ? val : avalon(win).scrollTop()) } else { node[method] = val } } } }) function getWindow(node) { return node.window && node.document ? node : node.nodeType === 9 ? node.defaultView || node.parentWindow : false; } //=============================css相关======================= var cssHooks = avalon.cssHooks = {} var prefixes = ["", "-webkit-", "-o-", "-moz-", "-ms-"] var cssMap = { "float": "cssFloat", background: "backgroundColor" } avalon.cssNumber = oneObject("columnCount,order,fillOpacity,fontWeight,lineHeight,opacity,orphans,widows,zIndex,zoom") avalon.cssName = function(name, host, camelCase) { if (cssMap[name]) { return cssMap[name] } host = host || root.style for (var i = 0, n = prefixes.length; i < n; i++) { camelCase = camelize(prefixes[i] + name) if (camelCase in host) { return (cssMap[name] = camelCase) } } return null } cssHooks["@:set"] = function(node, name, value) { try { //node.style.width = NaN;node.style.width = "xxxxxxx";node.style.width = undefine 在旧式IE下会抛异? node.style[name] = value } catch (e) { } } if (window.getComputedStyle) { cssHooks["@:get"] = function(node, name) { var ret, styles = getComputedStyle(node, null) if (styles) { ret = name === "filter" ? styles.getPropertyValue(name) : styles[name] if (ret === "") { ret = node.style[name] //其他浏览器需要我们手动取内联样式 } } return ret } cssHooks["opacity:get"] = function(node) { var ret = cssHooks["@:get"](node, "opacity") return ret === "" ? "1" : ret } } else { var rnumnonpx = /^-?(?:\d*\.)?\d+(?!px)[^\d\s]+$/i var rposition = /^(top|right|bottom|left)$/ var ie8 = !!window.XDomainRequest var salpha = "DXImageTransform.Microsoft.Alpha" var border = { thin: ie8 ? '1px' : '2px', medium: ie8 ? '3px' : '4px', thick: ie8 ? '5px' : '6px' } cssHooks["@:get"] = function(node, name) { //取得精确值,不过它有可能是带em,pc,mm,pt,%等单? var currentStyle = node.currentStyle var ret = currentStyle[name] if ((rnumnonpx.test(ret) && !rposition.test(ret))) { //①,保存原有的style.left, runtimeStyle.left, var style = node.style, left = style.left, rsLeft = node.runtimeStyle.left //②由于③处的style.left = xxx会影响到currentStyle.left? //因此把它currentStyle.left放到runtimeStyle.left? //runtimeStyle.left拥有最高优先级,不会style.left影响 node.runtimeStyle.left = currentStyle.left //③将精确值赋给到style.left,然后通过IE的另一个私有属?style.pixelLeft //得到单位为px的结果;fontSize的分支见//bugs.jquery.com/ticket/760 style.left = name === 'fontSize' ? '1em' : (ret || 0) ret = style.pixelLeft + "px" //④还?style.left,runtimeStyle.left style.left = left node.runtimeStyle.left = rsLeft } if (ret === "medium") { name = name.replace("Width", "Style") //border width 默认值为medium,即使其?" if (currentStyle[name] === "none") { ret = "0px" } } return ret === "" ? "auto" : border[ret] || ret } cssHooks["opacity:set"] = function(node, name, value) { node.style.filter = 'alpha(opacity=' + value * 100 + ')' node.style.zoom = 1 } cssHooks["opacity:get"] = function(node) { //这是最快的获取IE透明值的方式,不需要动用正则了? var alpha = node.filters.alpha || node.filters[salpha], op = alpha ? alpha.opacity : 100 return (op / 100) + "" //确保返回的是字符? } } "top,left".replace(rword, function(name) { cssHooks[name + ":get"] = function(node) { var computed = cssHooks["@:get"](node, name) return /px$/.test(computed) ? computed : avalon(node).position()[name] + "px" } }) var cssShow = { position: "absolute", visibility: "hidden", display: "block" } var rdisplayswap = /^(none|table(?!-c[ea]).+)/ function showHidden(node, array) { ////www.cnblogs.com/rubylouvre/archive/2012/10/27/2742529.html if (node.offsetWidth <= 0) { //opera.offsetWidth可能小于0 if (rdisplayswap.test(cssHooks["@:get"](node, "display"))) { var obj = { node: node } for (var name in cssShow) { obj[name] = node.style[name] node.style[name] = cssShow[name] } array.push(obj) } var parent = node.parentNode if (parent && parent.nodeType == 1) { showHidden(parent, array) } } } "Width,Height".replace(rword, function(name) { var method = name.toLowerCase(), clientProp = "client" + name, scrollProp = "scroll" + name, offsetProp = "offset" + name cssHooks[method + ":get"] = function(node, which, override) { var boxSizing = "content-box" if (typeof override === "string") { boxSizing = override } which = name === "Width" ? ["Left", "Right"] : ["Top", "Bottom"] switch (boxSizing) { case "content-box": return node["client" + name] - avalon.css(node, "padding" + which[0], true) - avalon.css(node, "padding" + which[1], true) case "padding-box": return node["client" + name] case "border-box": return node["offset" + name] case "margin-box": return node["offset" + name] + avalon.css(node, "margin" + which[0], true) + avalon.css(node, "margin" + which[1], true) } } cssHooks[method + "&get"] = function(node) { var hidden = []; showHidden(node, hidden); var val = cssHooks[method + ":get"](node) for (var i = 0, obj; obj = hidden[i++]; ) { node = obj.node for (var n in obj) { if (typeof obj[n] === "string") { node.style[n] = obj[n] } } } return val; } avalon.fn[method] = function(value) { //会忽视其display var node = this[0] if (arguments.length === 0) { if (node.setTimeout) { //取得窗口尺寸,IE9后可以用node.innerWidth /innerHeight代替 return node["inner" + name] || node.document.documentElement[clientProp] } if (node.nodeType === 9) { //取得页面尺寸 var doc = node.documentElement //FF chrome html.scrollHeight< body.scrollHeight //IE 标准模式 : html.scrollHeight> body.scrollHeight //IE 怪异模式 : html.scrollHeight 最大等于可视窗口多一点? return Math.max(node.body[scrollProp], doc[scrollProp], node.body[offsetProp], doc[offsetProp], doc[clientProp]) } return cssHooks[method + "&get"](node) } else { return this.css(method, value) } } avalon.fn["inner" + name] = function() { return cssHooks[method + ":get"](this[0], void 0, "padding-box") } avalon.fn["outer" + name] = function(includeMargin) { return cssHooks[method + ":get"](this[0], void 0, includeMargin === true ? "border-box" : "margin-box") } }) avalon.fn.offset = function() { //取得距离页面左右角的坐标 var node = this[0], doc = node && node.ownerDocument, win = doc.defaultView || doc.parentWindow, body = doc.body, root = doc.documentElement, box = { left: 0, top: 0 } if (!doc || !avalon.contains(root, node)) { return box } ////hkom.blog1.fc2.com/?mode=m&no=750 body的偏移量是不包含margin? //我们可以通过getBoundingClientRect来获得元素相对于client的rect. ////msdn.microsoft.com/en-us/library/ms536433.aspx if (typeof node.getBoundingClientRect !== "undefined") { box = node.getBoundingClientRect() // BlackBerry 5, iOS 3 (original iPhone) } //chrome/IE6: body.scrollTop, firefox/other: root.scrollTop var clientTop = root.clientTop || body.clientTop, clientLeft = root.clientLeft || body.clientLeft, scrollTop = Math.max(win.pageYOffset || 0, root.scrollTop, body.scrollTop), scrollLeft = Math.max(win.pageXOffset || 0, root.scrollLeft, body.scrollLeft) // 把滚动距离加到left,top中去? // IE一些版本中会自动为HTML元素加上2px的border,我们需要去掉它 // //msdn.microsoft.com/en-us/library/ms533564(VS.85).aspx return { top: box.top + scrollTop - clientTop, left: box.left + scrollLeft - clientLeft } } //==================================val相关============================ function getValType(el) { var ret = el.tagName.toLowerCase() return ret === "input" && /checkbox|radio/.test(el.type) ? "checked" : ret } var roption = /^]+))?)*\s+value[\s=]/i var valHooks = { "option:get": function(node) { //在IE11及W3C,如果没有指定value,那么node.value默认为node.text(存在trim作),但IE9-10则是取innerHTML(没trim操作) if (node.hasAttribute) { return node.hasAttribute("value") ? node.value : node.text.trim() } //specified并不可靠,因此通过分析outerHTML判定用户有没有显示定义value return roption.test(node.outerHTML) ? node.value : node.text }, "select:get": function(node, value) { var option, options = node.options, index = node.selectedIndex, getter = valHooks["option:get"], one = node.type === "select-one" || index < 0, values = one ? null : [], max = one ? index + 1 : options.length, i = index < 0 ? max : one ? index : 0 for (; i < max; i++) { option = options[i] //旧式IE在reset后不会改变selected,需要改用i === index判定 //我们过滤所有disabled的option元素,但在safari5下,如果设置select为disable,那么其所有孩子都disable //因此当一个元素为disable,需要检测其是否显式设置了disable及其父节点的disable情况 if ((option.selected || i === index) && !option.disabled) { value = getter(option) if (one) { return value } //收集所有selected值组成数组返? values.push(value) } } return values }, "select:set": function(node, values, optionSet) { values = [].concat(values) //强制转换为数? var getter = valHooks["option:get"] for (var i = 0, el; el = node.options[i++]; ) { if ((el.selected = values.indexOf(getter(el)) >= 0)) { optionSet = true } } if (!optionSet) { node.selectedIndex = -1 } } } /************************************************************************ * parseHTML * ************************************************************************/ var rtagName = /<([\w:]+)/, //取得其tagName rxhtml = /<(?!area|br|col|embed|hr|img|input|link|meta|param)(([\w:]+)[^>]*)\/>/ig, rcreate = W3C ? /[^\d\D]/ : /(<(?:script|link|style|meta|noscript))/ig, scriptTypes = oneObject("text/javascript", "text/ecmascript", "application/ecmascript", "application/javascript", "text/vbscript"), //需要处理套嵌关系的标签 rnest = /<(?:tb|td|tf|th|tr|col|opt|leg|cap|area)/ //parseHTML的辅助变? var tagHooks = { area: [1, ""], param: [1, ""], col: [2, "", "
"], legend: [1, "
"], option: [1, "